package org.elephantt.webby.security;

import org.apache.commons.codec.binary.Base64;
import org.elephantt.webby.StringUtil;

import java.io.*;

/**
 * A tamper-proof value. Use SealedValueService to generate and verify.
 */
public class SealedValue {
  private static final int maxValueLength = Integer.MAX_VALUE - 1;
  private static final int lengthBytes = Integer.SIZE/8;

  private final byte[] value;
  private final byte[] mac;

  public SealedValue(byte[] value, byte[] mac) {
    this.value = value;
    this.mac = mac;
    if (value.length > maxValueLength) throw new IllegalArgumentException("content too long, max length is " + maxValueLength);
  }

  public SealedValue(String valueStr, byte[] mac) throws UnsupportedEncodingException {
    this(valueStr.getBytes("UTF-8"), mac);
  }

  public byte[] getMac() {
    return mac;
  }

  public byte[] getValue() {
    return value;
  }

  public static SealedValue parseBase64String(String str) throws ParseException {
    byte[] buf = Base64.decodeBase64(str.getBytes());
    DataInput in = new DataInputStream(new ByteArrayInputStream(buf));
    try {
      int length = in.readInt();
      byte[] value = new byte[length];
      in.readFully(value);

      int macLength = buf.length - length - lengthBytes;
      if (macLength < 0) {
        throw new ParseException();
      }
      byte[] mac = new byte[macLength];
      in.readFully(mac);

      return new SealedValue(value, mac);
    } catch (EOFException e) {
      throw new ParseException("unexpected value");
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public byte[] getBytes() {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream(lengthBytes + value.length + mac.length);
      DataOutput out = new DataOutputStream(baos);
      out.writeInt(value.length);
      out.write(value);
      out.write(mac);
      return baos.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public String toBase64String() {
    return StringUtil.bytesToString(Base64.encodeBase64(getBytes()));
  }

  public String toString() {
    return "SealedValue{" +
            "value=" + value +
            ", mac=" + mac +
            '}';
  }

  public static void main(String[] args) throws ParseException, UnsupportedEncodingException {
    SealedValue sealedValue1 = new SealedValue("foo", "bar".getBytes("UTF-8"));
    String str = sealedValue1.toBase64String();
    System.out.println("base64 string: " + str);

    SealedValue t2 = SealedValue.parseBase64String(str);
    System.out.println(new String(t2.getValue(), "UTF-8"));
  }
}
